﻿/* SCE CONFIDENTIAL
 * $PSLibId$
 * Copyright (C) 2011 Sony Computer Entertainment Inc.
 * All Rights Reserved.
 */

#include "common.h"
#include <display.h>
#include <ctrl.h>
#include <libdbgfont.h>
#include <libdbg.h>
#include <string.h>
#include <stdlib.h>

// Data structure to pass through the display queue
typedef struct DisplayData
{
	void *address;				///< Framebuffer address
	uint32_t width;				///< Framebuffer width
	uint32_t height;			///< Framebuffer height
	uint32_t strideInPixels;	///< Framebuffer stride in pixels
	uint32_t flipMode;			///< From #FlipMode
} DisplayData;

// heap memblocks
SceUID							g_lpddrUid = 0;
SceUID							g_cdramUid = 0;

// libgxm context
void							*g_contextHostMem = NULL;
void							*g_contextVdmMem = NULL;
void							*g_contextVertexMem = NULL;
void							*g_contextFragmentMem = NULL;
void							*g_contextFragmentUsseMem = NULL;
SceGxmContext					*g_context = NULL;

// libgxm shader patcher
SceGxmShaderPatcher				*g_shaderPatcher = NULL;

// libgxm display queue
void							*g_displayBufferData[DISPLAY_BUFFER_COUNT];
SceGxmColorSurface				g_displaySurface[DISPLAY_BUFFER_COUNT];
SceGxmSyncObject				*g_displayBufferSync[DISPLAY_BUFFER_COUNT];
uint32_t						g_displayFrontBufferIndex = DISPLAY_BUFFER_COUNT - 1;
uint32_t						g_displayBackBufferIndex = 0;

// Depth buffer for display surface
void							*g_mainDepthBufferData = NULL;
SceGxmDepthStencilSurface		g_mainDepthSurface;

// libgxm main render target
SceGxmRenderTarget				*g_mainRenderTarget = NULL;

// Callback functions to allocate memory for the shader patcher
static void *patcherHostAlloc(void *userData, uint32_t size);
static void patcherHostFree(void *userData, void *mem);
static void *patcherBufferAlloc(void *userData, uint32_t size);
static void *patcherVertexUsseAlloc(void *userData, uint32_t size, uint32_t *usseOffset);
static void *patcherFragmentUsseAlloc(void *userData, uint32_t size, uint32_t *usseOffset);
static void patcherFree(void *userData, void *mem);

int initDbgFont(void)
{
	int err = SCE_OK;
	UNUSED(err);

#if !defined(DISABLE_DEBUG_FONT)
	// initialize structure
	SceDbgFontConfig config;
	memset(&config, 0, sizeof(SceDbgFontConfig));
	config.fontSize = SCE_DBGFONT_FONTSIZE_DEFAULT;
	err = sceDbgFontInit(&config);
	SCE_DBG_ASSERT(err == SCE_OK);
#endif

	// done
	return err;
}

int termDbgFont(void)
{
	int err = SCE_OK;
	UNUSED(err);

#if !defined(DISABLE_DEBUG_FONT)
	// exit
	err = sceDbgFontExit();
	SCE_DBG_ASSERT(err == SCE_OK);
#endif

	// done
	return err;
}

static void displayCallback(const void *callbackData)
{
	int err = SCE_OK;
	UNUSED(err);

	// Cast the parameters back
	const DisplayData *displayData = (const DisplayData *)callbackData;

	// Render debug text now GPU rendering has finished
	renderDbgFont();

	// Render copyright debug text
//	sceDbgFontPrint(30, displayData->height - 45, 0xffffffff, (const SceChar8 *)"SCE CONFIDENTIAL");
//	sceDbgFontPrint(30, displayData->height - 30, 0xffffffff, (const SceChar8 *)"Copyright(C) 2012 Sony Computer Entertainment Inc.");

	// Flush debug text
	SceDbgFontFrameBufInfo info;
	memset(&info, 0, sizeof(info));
	info.frameBufAddr = (SceUChar8 *)displayData->address;
	info.frameBufPitch = displayData->strideInPixels;
	info.frameBufWidth = displayData->width;
	info.frameBufHeight = displayData->height;
	info.frameBufPixelformat = DISPLAY_DBGFONT_FORMAT;
#if !defined(DISABLE_DEBUG_FONT)
	err = sceDbgFontFlush(&info);
	SCE_DBG_ASSERT(err == SCE_OK);
#endif

	// Check this buffer has been displayed for the necessary number of VSYNCs
	// (Avoids queuing a flip before the second VSYNC has happened)
	if (displayData->flipMode == FLIP_MODE_VSYNC_2) {
		err = sceDisplayWaitSetFrameBufMulti(2);
	}

	// Swap to the new buffer
	SceDisplayFrameBuf framebuf;
	memset(&framebuf, 0x00, sizeof(SceDisplayFrameBuf));
	framebuf.size        = sizeof(SceDisplayFrameBuf);
	framebuf.base        = displayData->address;
	framebuf.pitch       = displayData->strideInPixels;
	framebuf.pixelformat = DISPLAY_PIXEL_FORMAT;
	framebuf.width       = displayData->width;
	framebuf.height      = displayData->height;
	err = sceDisplaySetFrameBuf(&framebuf,
		(displayData->flipMode == FLIP_MODE_HSYNC)
			? SCE_DISPLAY_UPDATETIMING_NEXTHSYNC
			: SCE_DISPLAY_UPDATETIMING_NEXTVSYNC);
	SCE_DBG_ASSERT(err == SCE_OK);

	// Block this callback until the swap has occurred and the old buffer
	// is no longer displayed
	if (displayData->flipMode != FLIP_MODE_HSYNC) {
		err = sceDisplayWaitSetFrameBuf();
		SCE_DBG_ASSERT(err == SCE_OK);
	}
}

static void initGxmLibrary(void)
{
	int err = SCE_OK;
	UNUSED(err);

	// set up parameters
	SceGxmInitializeParams initializeParams;
	memset(&initializeParams, 0, sizeof(SceGxmInitializeParams));
	initializeParams.flags							= 0;
	initializeParams.displayQueueMaxPendingCount	= DISPLAY_MAX_PENDING_SWAPS;
	initializeParams.displayQueueCallback			= displayCallback;
	initializeParams.displayQueueCallbackDataSize	= sizeof(DisplayData);
	initializeParams.parameterBufferSize			= SCE_GXM_DEFAULT_PARAMETER_BUFFER_SIZE;

	// start libgxm
	err = sceGxmInitialize(&initializeParams);
	SCE_DBG_ASSERT(err == SCE_OK);
}

static void initHeaps(void)
{
	int err = SCE_OK;
	UNUSED(err);

	// set up an empty heap
	heapInitialize();

	// allocate memory blocks
	g_lpddrUid = sceKernelAllocMemBlock(
		"SampleLpddr",
		SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE,
		HEAP_SIZE_LPDDR_R + HEAP_SIZE_LPDDR_RW + HEAP_SIZE_VERTEX_USSE + HEAP_SIZE_FRAGMENT_USSE,
		NULL);
	SCE_DBG_ASSERT(g_lpddrUid >= SCE_OK);
	g_cdramUid = sceKernelAllocMemBlock(
		"SampleCdram",
		SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RWDATA,
		HEAP_SIZE_CDRAM_RW,
		NULL);
	SCE_DBG_ASSERT(g_cdramUid >= SCE_OK);

	// grab the base addresses
	uint8_t *lpddrMem = NULL;
	uint8_t *cdramMem = NULL;
	err = sceKernelGetMemBlockBase(g_lpddrUid, (void **)&lpddrMem);
	SCE_DBG_ASSERT(err == SCE_OK);
	err = sceKernelGetMemBlockBase(g_cdramUid, (void **)&cdramMem);
	SCE_DBG_ASSERT(err == SCE_OK);

	// map and add each block
	uint32_t usseOffset;

	err = sceGxmMapMemory(lpddrMem, HEAP_SIZE_LPDDR_R, SCE_GXM_MEMORY_ATTRIB_READ);
	SCE_DBG_ASSERT(err == SCE_OK);
	heapExtend(HEAP_TYPE_LPDDR_R, lpddrMem, HEAP_SIZE_LPDDR_R);
	lpddrMem += HEAP_SIZE_LPDDR_R;

	err = sceGxmMapMemory(lpddrMem, HEAP_SIZE_LPDDR_RW, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE);
	SCE_DBG_ASSERT(err == SCE_OK);
	heapExtend(HEAP_TYPE_LPDDR_RW, lpddrMem, HEAP_SIZE_LPDDR_RW);
	lpddrMem += HEAP_SIZE_LPDDR_RW;

	err = sceGxmMapMemory(cdramMem, HEAP_SIZE_CDRAM_RW, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE);
	SCE_DBG_ASSERT(err == SCE_OK);
	heapExtend(HEAP_TYPE_CDRAM_RW, cdramMem, HEAP_SIZE_CDRAM_RW);
	cdramMem += HEAP_SIZE_CDRAM_RW;

	err = sceGxmMapVertexUsseMemory(lpddrMem, HEAP_SIZE_VERTEX_USSE, &usseOffset);
	SCE_DBG_ASSERT(err == SCE_OK);
	heapExtend(HEAP_TYPE_VERTEX_USSE, lpddrMem, HEAP_SIZE_VERTEX_USSE, usseOffset);
	lpddrMem += HEAP_SIZE_VERTEX_USSE;

	err = sceGxmMapFragmentUsseMemory(lpddrMem, HEAP_SIZE_FRAGMENT_USSE, &usseOffset);
	SCE_DBG_ASSERT(err == SCE_OK);
	heapExtend(HEAP_TYPE_FRAGMENT_USSE, lpddrMem, HEAP_SIZE_FRAGMENT_USSE, usseOffset);
	lpddrMem += HEAP_SIZE_FRAGMENT_USSE;
}

static void createGxmContext(void)
{
	int err = SCE_OK;
	UNUSED(err);

	// allocate host memory
	g_contextHostMem = malloc(SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE);

	// allocate ring buffer memory using default sizes
	uint32_t fragmentUsseOffset;
	g_contextVdmMem = heapAlloc(HEAP_TYPE_LPDDR_R, SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE, 4);
	g_contextVertexMem = heapAlloc(HEAP_TYPE_LPDDR_R, SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE, 4);
	g_contextFragmentMem = heapAlloc(HEAP_TYPE_LPDDR_R, SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE, 4);
	g_contextFragmentUsseMem = heapAlloc(HEAP_TYPE_FRAGMENT_USSE, SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE, 4, &fragmentUsseOffset);

	// set up parameters
	SceGxmContextParams contextParams;
	memset(&contextParams, 0, sizeof(SceGxmContextParams));
	contextParams.hostMem						= g_contextHostMem;
	contextParams.hostMemSize					= SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE;
	contextParams.vdmRingBufferMem				= g_contextVdmMem;
	contextParams.vdmRingBufferMemSize			= SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE;
	contextParams.vertexRingBufferMem			= g_contextVertexMem;
	contextParams.vertexRingBufferMemSize		= SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE;
	contextParams.fragmentRingBufferMem			= g_contextFragmentMem;
	contextParams.fragmentRingBufferMemSize		= SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE;
	contextParams.fragmentUsseRingBufferMem		= g_contextFragmentUsseMem;
	contextParams.fragmentUsseRingBufferMemSize	= SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE;
	contextParams.fragmentUsseRingBufferOffset	= fragmentUsseOffset;

	// create the context
	err = sceGxmCreateContext(&contextParams, &g_context);
	SCE_DBG_ASSERT(err == SCE_OK);
}

static void createGxmShaderPatcher(void)
{
	int err = SCE_OK;
	UNUSED(err);

	// create a shader patcher
	SceGxmShaderPatcherParams patcherParams;
	memset(&patcherParams, 0, sizeof(SceGxmShaderPatcherParams));
	patcherParams.userData					= NULL;
	patcherParams.hostAllocCallback			= &patcherHostAlloc;
	patcherParams.hostFreeCallback			= &patcherHostFree;
	patcherParams.bufferAllocCallback		= &patcherBufferAlloc;
	patcherParams.bufferFreeCallback		= &patcherFree;
	patcherParams.bufferMem					= NULL;
	patcherParams.bufferMemSize				= NULL;
	patcherParams.vertexUsseAllocCallback	= &patcherVertexUsseAlloc;
	patcherParams.vertexUsseFreeCallback	= &patcherFree;
	patcherParams.vertexUsseMem				= NULL;
	patcherParams.vertexUsseMemSize			= NULL;
	patcherParams.vertexUsseOffset			= NULL;
	patcherParams.fragmentUsseAllocCallback	= &patcherFragmentUsseAlloc;
	patcherParams.fragmentUsseFreeCallback	= &patcherFree;
	patcherParams.fragmentUsseMem			= NULL;
	patcherParams.fragmentUsseMemSize		= NULL;
	patcherParams.fragmentUsseOffset		= NULL;

	err = sceGxmShaderPatcherCreate(&patcherParams, &g_shaderPatcher);
	SCE_DBG_ASSERT(err == SCE_OK);
}

int initGxm(void)
{
	int err = SCE_OK;
	UNUSED(err);

	// initialize libgxm
	initGxmLibrary();

	// initialize our sample heaps
	initHeaps();

	// create a rendering context
	createGxmContext();

	// create a shader patcher
	createGxmShaderPatcher();

	// allocate memory and sync objects for display buffers
	for (uint32_t i = 0; i < DISPLAY_BUFFER_COUNT; ++i) {
		// allocate memory for display
		g_displayBufferData[i] = heapAlloc(
			HEAP_TYPE_CDRAM_RW,
			4*DISPLAY_STRIDE_IN_PIXELS*DISPLAY_HEIGHT,
			256);

		// memset the buffer to black
		for (uint32_t y = 0; y < DISPLAY_HEIGHT; ++y) {
			uint32_t *row = (uint32_t *)g_displayBufferData[i] + y*DISPLAY_STRIDE_IN_PIXELS;
			for (uint32_t x = 0; x < DISPLAY_WIDTH; ++x) {
				row[x] = 0xff000000;
			}
		}

		// initialize a color surface for this display buffer
		err = sceGxmColorSurfaceInit(
			&g_displaySurface[i],
			DISPLAY_COLOR_FORMAT,
			SCE_GXM_COLOR_SURFACE_LINEAR,
			(MSAA_MODE == SCE_GXM_MULTISAMPLE_NONE) ? SCE_GXM_COLOR_SURFACE_SCALE_NONE : SCE_GXM_COLOR_SURFACE_SCALE_MSAA_DOWNSCALE,
			SCE_GXM_OUTPUT_REGISTER_SIZE_32BIT,
			DISPLAY_WIDTH,
			DISPLAY_HEIGHT,
			DISPLAY_STRIDE_IN_PIXELS,
			g_displayBufferData[i]);
		SCE_DBG_ASSERT(err == SCE_OK);

		// create a sync object that we will associate with this buffer
		err = sceGxmSyncObjectCreate(&g_displayBufferSync[i]);
		SCE_DBG_ASSERT(err == SCE_OK);
	}

	// create a depth buffer
	const uint32_t alignedWidth = ALIGN(DISPLAY_WIDTH, SCE_GXM_TILE_SIZEX);
	const uint32_t alignedHeight = ALIGN(DISPLAY_HEIGHT, SCE_GXM_TILE_SIZEY);
	uint32_t sampleCount = alignedWidth*alignedHeight;
	uint32_t depthStrideInSamples = alignedWidth;
	if (MSAA_MODE == SCE_GXM_MULTISAMPLE_4X) {
		// samples increase in X and Y
		sampleCount *= 4;
		depthStrideInSamples *= 2;
	} else if (MSAA_MODE == SCE_GXM_MULTISAMPLE_2X) {
		// samples increase in Y only
		sampleCount *= 2;
	}
	g_mainDepthBufferData = heapAlloc(
		HEAP_TYPE_LPDDR_RW,
		4*sampleCount,
		SCE_GXM_DEPTHSTENCIL_SURFACE_ALIGNMENT);

	// initialize depth surface
	err = sceGxmDepthStencilSurfaceInit(
		&g_mainDepthSurface,
		SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24,
		SCE_GXM_DEPTH_STENCIL_SURFACE_TILED,
		depthStrideInSamples,
		g_mainDepthBufferData,
		NULL);

	// swap to the current front buffer with VSYNC
	// (also ensures that future calls with HSYNC are successful)
	SceDisplayFrameBuf framebuf;
	memset(&framebuf, 0x00, sizeof(SceDisplayFrameBuf));
	framebuf.size        = sizeof(SceDisplayFrameBuf);
	framebuf.base        = g_displayBufferData[g_displayFrontBufferIndex];
	framebuf.pitch       = DISPLAY_STRIDE_IN_PIXELS;
	framebuf.pixelformat = DISPLAY_PIXEL_FORMAT;
	framebuf.width       = DISPLAY_WIDTH;
	framebuf.height      = DISPLAY_HEIGHT;
	err = sceDisplaySetFrameBuf(&framebuf, SCE_DISPLAY_UPDATETIMING_NEXTVSYNC);
	SCE_DBG_ASSERT(err == SCE_OK);
	err = sceDisplayWaitSetFrameBuf();
	SCE_DBG_ASSERT(err == SCE_OK);

	// create a render target that describes the tiling setup we want to use
	g_mainRenderTarget = createRenderTarget(DISPLAY_WIDTH, DISPLAY_HEIGHT, MSAA_MODE);

	// done
	return err;
}

int termGxm(void)
{
	int err = SCE_OK;
	UNUSED(err);

	// destroy render target
	destroyRenderTarget(g_mainRenderTarget);

	// destroy depth buffer
	heapFree(g_mainDepthBufferData);

	// wait for display processing to finish before deallocating buffers
	err = sceGxmDisplayQueueFinish();
	SCE_DBG_ASSERT(err == SCE_OK);

	// free the display buffers and sync objects
	for (uint32_t i = 0; i < DISPLAY_BUFFER_COUNT; ++i) {
		// free color surface
		heapFree(g_displayBufferData[i]);

		// destroy sync object
		err = sceGxmSyncObjectDestroy(g_displayBufferSync[i]);
		SCE_DBG_ASSERT(err == SCE_OK);
	}

	// destroy the shader patcher
	err = sceGxmShaderPatcherDestroy(g_shaderPatcher);
	SCE_DBG_ASSERT(err == SCE_OK);

	// destroy the rendering context
	err = sceGxmDestroyContext(g_context);
	SCE_DBG_ASSERT(err == SCE_OK);
	heapFree(g_contextFragmentUsseMem);
	heapFree(g_contextFragmentMem);
	heapFree(g_contextVertexMem);
	heapFree(g_contextVdmMem);
	free(g_contextHostMem);

	// destroy heaps
	heapTerminate();
	sceKernelFreeMemBlock(g_lpddrUid);
	sceKernelFreeMemBlock(g_cdramUid);

	// terminate libgxm
	err = sceGxmTerminate();
	SCE_DBG_ASSERT(err == SCE_OK);

	// done
	return err;
}

int cycleDisplayBuffers(FlipMode flipMode, uint32_t width, uint32_t height, uint32_t strideInPixels)
{
	int err = SCE_OK;
	UNUSED(err);

	// queue the display swap for this frame
	DisplayData displayData;
	displayData.address = g_displayBufferData[g_displayBackBufferIndex];
	displayData.width = width;
	displayData.height = height;
	displayData.strideInPixels = strideInPixels;
	displayData.flipMode = flipMode;
	err = sceGxmDisplayQueueAddEntry(
		g_displayBufferSync[g_displayFrontBufferIndex],		// front buffer is OLD buffer
		g_displayBufferSync[g_displayBackBufferIndex],		// back buffer is NEW buffer
		&displayData);
	SCE_DBG_ASSERT(err == SCE_OK);

	// update buffer indices
	g_displayFrontBufferIndex = g_displayBackBufferIndex;
	g_displayBackBufferIndex = (g_displayBackBufferIndex + 1) % DISPLAY_BUFFER_COUNT;

	// done
	return err;
}

SceGxmRenderTarget *createRenderTarget(uint32_t width, uint32_t height, SceGxmMultisampleMode msaaMode)
{
	int err = SCE_OK;
	UNUSED(err);

	// set up parameters
	SceGxmRenderTargetParams params;
	memset(&params, 0, sizeof(SceGxmRenderTargetParams));
	params.flags				= 0;
	params.width				= width;
	params.height				= height;
	params.scenesPerFrame		= 1;
	params.multisampleMode		= msaaMode;
	params.multisampleLocations	= 0;
	params.driverMemBlock		= SCE_UID_INVALID_UID;

	// create the render target
	SceGxmRenderTarget *renderTarget;
	err = sceGxmCreateRenderTarget(&params, &renderTarget);
	SCE_DBG_ASSERT(err == SCE_OK);
	return renderTarget;
}

void destroyRenderTarget(SceGxmRenderTarget *renderTarget)
{
	int err = SCE_OK;
	UNUSED(err);

	// destroy the render target
	err = sceGxmDestroyRenderTarget(renderTarget);
	SCE_DBG_ASSERT(err == SCE_OK);
}

void *patcherHostAlloc(void *userData, uint32_t size)
{
	UNUSED(userData);
	return malloc(size);
}

void patcherHostFree(void *userData, void *mem)
{
	UNUSED(userData);
	free(mem);
}

void *patcherBufferAlloc(void *userData, uint32_t size)
{
	UNUSED(userData);
	return heapAlloc(HEAP_TYPE_LPDDR_RW, size, 4);
}

void *patcherVertexUsseAlloc(void *userData, uint32_t size, uint32_t *usseOffset)
{
	UNUSED(userData);
	return heapAlloc(HEAP_TYPE_VERTEX_USSE, size, SCE_GXM_USSE_ALIGNMENT, usseOffset);
}

void *patcherFragmentUsseAlloc(void *userData, uint32_t size, uint32_t *usseOffset)
{
	UNUSED(userData);
	return heapAlloc(HEAP_TYPE_FRAGMENT_USSE, size, SCE_GXM_USSE_ALIGNMENT, usseOffset);
}

void patcherFree(void *userData, void *mem)
{
	UNUSED(userData);
	heapFree(mem);
}

